package com.zzyl.nursing.service.impl;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.exception.base.BaseException;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.framework.web.domain.server.Sys;
import com.zzyl.nursing.config.WebSocketServer;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.AlertDataMapper;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.vo.AlertNotifyVo;
import com.zzyl.system.mapper.SysUserMapper;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * 报警规则Service业务层处理
 *
 * @author mr.lcc
 * @date 2025-05-29
 */
@Service
@Slf4j
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {
    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private DeviceMapper deviceMapper;

    /**
     * 查询报警规则
     *
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id) {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     *
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule) {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule) {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     *
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids) {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     *
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id) {
        return removeById(id) ? 1 : 0;
    }


    /**
     * 报警数据表
     */
    @Autowired
    private IAlertDataService alertDataService;

    /**
     * 缓存
     */
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Value("${alertRule.roleAdmin}")
    private String roleAdmin;


    @Value("${alertRule.roleRepair}")
    private String roleRepair;

    @Autowired
    private WebSocketServer webSocketServer;


    /**
     * 定时进行设备数据对应的报警规则校验，将报警数据保存到对应表中
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void alertRuleFilter() {

        // 1.查询报警表中所有的报警规则
        Long ruleCount = this.lambdaQuery().eq(AlertRule::getStatus, 1).count();
        if (ruleCount <= 0) {
            log.error("[115:查询报警规则表为空，无需处理！]");
            return;
        }

        // 2.查询缓存中所有上报的数据
        List<Object> values = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        if (CollUtil.isEmpty(values)) {
            log.error("[131:查询缓存上报数据为空，无需处理！]");
            return;
        }

        // 3.数据格式化标准处理
        ArrayList<DeviceData> deviceDataAllList = new ArrayList<>();
        values.forEach(v -> {
            List<DeviceData> deviceDataList = JSONUtil.toList((String) v, DeviceData.class);
            // 追加数据
            deviceDataAllList.addAll(deviceDataList);
        });

        // 4.获取到全部的设备数据，进行过滤处理
        for (DeviceData deviceData : deviceDataAllList) {
            // 4.1 设备上报数据是否太久（由业务决定，可以改变）
            // 单位：秒。设备数据上传超过10分钟直接不处理。
            int limit = 6000;
            // LocalDateTimeUtil.between()算两个时间之间的差值，单位是秒。-- 糊涂工具类
            long between = LocalDateTimeUtil.between(deviceData.getAlarmTime(), LocalDateTime.now(), ChronoUnit.SECONDS);
            if (between > limit) {
                // 如果时间差 between 超过了 limit（即 6000 秒），说明这个设备的报警数据已经太老了，不再处理。
                log.error("[157:设备上报数据超过10分钟]");
                continue;
            }

            // 4.2 查询上报数据对应的设备绑定的全部报警规则数据

            // 4.2.1 查询当前上报数据对应的产品类型下同样的全部规则数据
            List<AlertRule> productAlertRuleList = this.lambdaQuery()
                    // 产品Key
                    .eq(AlertRule::getProductKey, deviceData.getProductKey())
                    // 功能标识
                    .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                    // 设备 -1表示该类型的所有设备并且状态为 1启用的
                    .eq(AlertRule::getIotId, -1)
                    .eq(AlertRule::getStatus, 1)
                    .list();

            if (CollUtil.isEmpty(productAlertRuleList)) {
                productAlertRuleList = new ArrayList<>();
            }

            // 4.2.2 查询当前上报数据对应的设备下绑定的规则数据
            List<AlertRule> deviceAlterRuleList = this.lambdaQuery()
                    .eq(AlertRule::getProductKey, deviceData.getProductKey())
                    .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                    .eq(AlertRule::getIotId, deviceData.getIotId())
                    .eq(AlertRule::getStatus, 1)
                    .list();

            if (CollUtil.isEmpty(deviceAlterRuleList)) {
                deviceAlterRuleList = new ArrayList<>();
            }

            // 4.2.3 合并所有报警规则
            productAlertRuleList.addAll(deviceAlterRuleList);

            // 4.2.4 判断规则是否为空
            if (CollUtil.isEmpty(productAlertRuleList)) {
                log.error("[193定时报警规则校验]当前上报数据对应的设备没有匹配到任何报警规则，无需处理。");
                continue;
            }

            // 4.2.5 对规则进行去重，准备开始四层校验规则。
            List<AlertRule> allAlterRuleList = CollUtil.distinct(productAlertRuleList);
            // 5.开始四层数据校验
            for (AlertRule alertRule : allAlterRuleList) {
                // 5.1 第一层校验：判断生效时间
                // 生效时间段，格式为：00:00:00~23:59:59
                String period = alertRule.getAlertEffectivePeriod();
                // 比较上报的时间段
                String[] periodArr = period.split("~");
                // 开始时间
                LocalTime begin = LocalTime.parse(periodArr[0]);
                // 结束时间
                LocalTime end = LocalTime.parse(periodArr[1]);
                LocalTime alarmTime = deviceData.getAlarmTime().toLocalTime();
                if (alarmTime.isBefore(begin) || alarmTime.isAfter(end)) {
                    log.error("[214定时报警规则校验]数据上报时间不在规则生效时间内，无需处理");
                    continue;
                }

                // 5.2 第二层校验：检验数据是否达到阈值（就是触发报警的数值）
                // 设备上报的数据
                Double deviceDateValue = Double.valueOf(deviceData.getDataValue());
                // 规则定义的数据
                Double alterRuleValue = alertRule.getValue();
                // 规则中的运算符号
                String operator = alertRule.getOperator();
                // 比较
                int compared = NumberUtil.compare(deviceDateValue, alterRuleValue);

                // x大于y，结果就是1； x等于y，结果就是0； x小于y，结果就是-1
                if ((operator.equals(">=") && compared >= 0) || (operator.equals("<") && compared < 0)) {
                    // 5.3 第三层校验：判断沉默周期（沉默周期就是触发报警后的冷却时间）
                    // 检查沉默周期是否存在，存在就直接结束，组装redis中的key
                    String silentKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_SILENT_PREFIX, deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());
                    // 获取redis中制定key的值
                    String silentVal = redisTemplate.opsForValue().get(silentKey);
                    if (StringUtils.isNotEmpty(silentVal)) {
                        log.error("[238定时报警规则校验]沉默周期还未结束，无需处理！");
                        continue;
                    } else {
                        // 5.4 第四层校验：判断持续周期（持续周期就是触发报警达到的次数就开始报警处理）
                        // 判断持续周期对应的次数是否达到（没达到则直接结束）
                        String countKey = CharSequenceUtil.format(CacheConstants.IOT_ALERT_COUNT_PREFIX, deviceData.getIotId(), deviceData.getFunctionId(), alertRule.getId());
                        // 计数
                        Long increment = redisTemplate.opsForValue().increment(countKey);
                        if (increment < alertRule.getDuration()) {
                            log.error("[247持续周期]目前持续周期为：{}次，还未到达！", increment);
                            continue;
                        }

                        // 保存沉默周期到Redis当中 生效日期10分钟
                        redisTemplate.opsForValue().setIfAbsent(silentKey, "123", alertRule.getAlertSilentPeriod() * 60, TimeUnit.SECONDS);

                        // 删除Redis中持续缓存的默认周期数据
                        redisTemplate.delete(countKey);

                        // 查询老人的护理人员
                        List<Long> nursingIds = null;

                        // 如果是老人异常数据，则查询对应的医护人员。
                        if (alertRule.getAlertDataType() == 0) {
                            // 如果设备是移动设备，则直接查到老人对应的护理人员。
                            if (deviceData.getLocationType() == 0) {
                                nursingIds = deviceMapper.selectNuringIdsByMoveIotId(deviceData.getIotId());
                            } else {
                                // 如果设备是固定设备，则需要关联床位查询老人对应的护理人员。
                                nursingIds = deviceMapper.selectNuringIdsByStopIotId(deviceData.getIotId());
                            }
                        } else {
                            // 如果是设备异常数据，则查询维修工。
                            nursingIds = sysUserRoleMapper.selectUserIdsByRoleName(roleRepair);
                        }

                        // 查询超级管理员
                        List<Long> adminUserIds = sysUserRoleMapper.selectUserIdsByRoleName(roleAdmin);

                        // 合并查询到的用户id
                        nursingIds.addAll(adminUserIds);

                        // 去重用户ID（查看异常报警的所有用户ID）
                        ArrayList<Long> nursingIdList = CollUtil.distinct(nursingIds);

                        // 封装报警基础数据对象
                        AlertData alertData = new AlertData();
                        BeanUtils.copyProperties(deviceData, alertData);
                        // 设置id为空防止插入数据是存在Id导致失败
                        alertData.setId(null);
                        // 设备数据类型 0-老人异常数据 1-设备异常数据
                        alertData.setType(alertRule.getAlertDataType());
                        // 规则Id
                        alertData.setAlertRuleId(alertRule.getId());
                        // 处理状态，默认为未处理 0-待处理  1-已处理
                        alertData.setStatus(0);

                        String reasonTemplate = "功能{}{}{}，持续了{}个周期才报警，沉默周期：{}";
                        String alterReason = CharSequenceUtil.format(reasonTemplate,
                                alertRule.getFunctionId(),
                                alertRule.getOperator(),
                                alertRule.getValue(),
                                alertRule.getDuration(),
                                alertRule.getAlertSilentPeriod()
                        );

                        // 报警原因
                        alertData.setAlertReason(alterReason);

                        // 所有用户报警数据的集合
                        List<AlertData> alertDataList = new ArrayList();
                        for (Long userId : nursingIdList) {
                            // 封装用户报警数据，设置用户id
                            AlertData userAlertData = new AlertData();
                            BeanUtils.copyProperties(alertData, userAlertData);
                            userAlertData.setUserId(userId);
                            alertDataList.add(userAlertData);
                        }

                        // 批量保存所有用户报警数据
                        boolean saveBatch = alertDataService.saveBatch(alertDataList);
                        if (!saveBatch) {
                            throw new BaseException("保存用户保存数据失败");
                        }

                        log.info("[347]开始推送报警消息到指定用户！");
                        AlertNotifyVo notifyVo = BeanUtil.toBean(deviceData, AlertNotifyVo.class);
                        notifyVo.setAccessLocation(deviceData.getRemark());
                        // 通知类型：0-解除报警 1-报警
                        notifyVo.setNotifyType(1);
                        // 异常数据类型
                        notifyVo.setAlertDataType(alertRule.getAlertDataType());
                        // 功能名称
                        notifyVo.setFunctionName(alertRule.getFunctionName());
                        notifyVo.setIsAllConsumer(false);

                        // 开始通知消息
                        for (Long userId : nursingIdList) {
                            webSocketServer.sendUserMessage(userId.toString(), JSONUtil.toJsonStr(notifyVo));
                        }

                    }
                } else {
                    log.error("[定时报警规则校验]数据上报的值没有达到规则阈值，无需处理");
                    continue;
                }
            }
        }
    }
}
